---
sidebar_label: Backend guide
sidebar_position: 3
description: 'Learn how to authenticate requests in your backend by verifying JWTs (JSON Web Token) using a JWK (JSON Web Key Set).'
---

import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';

# Backend guide

After a successful login Hanko issues a cookie containing a JSON Web Token
([JWT](https://datatracker.ietf.org/doc/html/rfc7519)). You can use this JWT to authenticate
requests on your backend. To do so, first retrieve the JSON Web Key Set
([JWKS](https://datatracker.ietf.org/doc/html/rfc7517))
containing the public keys used to verify the JWT from the Hanko API's `.well-known/jwks.json` endpoint.
Then use the JWKS to verify the JWT using a library for the programming language of your choice.

```mdx-code-block
<Tabs>
<TabItem value="go" label="Go">
```

This is an example of a custom middleware in a Go-based backend using
[Echo](https://echo.labstack.com/) and the [lestrrat-go/jwx](https://github.com/lestrrat-go/jwx) package:

```go showLineNumbers
import (
	"context"
	"fmt"
	"log"
	"net/http"
	"github.com/labstack/echo/v4"
	"github.com/lestrrat-go/jwx/v2/jwk"
	"github.com/lestrrat-go/jwx/v2/jwt"
)

func SessionMiddleware() echo.MiddlewareFunc {
  return func(next echo.HandlerFunc) echo.HandlerFunc {
    return func(c echo.Context) error {
      cookie, err := c.Cookie("hanko")
      if err == http.ErrNoCookie {
        return c.Redirect(http.StatusTemporaryRedirect, "/unauthorized")
      }
      if err != nil {
        return err
      }
      // replace "hankoApiURL" with your API URL
      set, err := jwk.Fetch(
        context.Background(),
        fmt.Sprintf("%v/.well-known/jwks.json", hankoApiURL)
      )
      if err != nil {
        return err
      }

      token, err := jwt.Parse([]byte(cookie.Value), jwt.WithKeySet(set))
      if err != nil {
        return c.Redirect(http.StatusTemporaryRedirect, "/unauthorized")
      }

      log.Printf("session for user '%s' verified successfully", token.Subject())

      c.Set("token", cookie.Value)
      c.Set("user", token.Subject())

      return next(c)
    }
  }
}
```

```mdx-code-block
</TabItem>
<TabItem value="node" label="Node.js">
```

This is an example of using a custom middleware in an [express](https://expressjs.com/)-based backend using
the [express-jwt](https://www.npmjs.com/package/express-jwt) and [jwks-rsa](https://www.npmjs.com/package/jwks-rsa)
packages:

```javascript showLineNumbers
const Express = require('express');
const { expressjwt: jwt } = require('express-jwt');
const jwksRsa = require('jwks-rsa');
const cookieParser = require('cookie-parser');

const jwksHost = process.env.HANKO_API_URL;

const app = new Express();
app.use(cookieParser());
app.use(
  jwt({
    secret: jwksRsa.expressJwtSecret({
      cache: true,
      rateLimit: true,
      jwksRequestsPerMinute: 2,
      jwksUri: `${jwksHost}/.well-known/jwks.json`
    }),
    algorithms: ['RS256'],
    getToken: function fromCookieOrHeader(req) {
      if (req.headers.authorization && req.headers.authorization.split(' ')[0] === 'Bearer') {
        return req.headers.authorization.split(' ')[1];
      } else if (req.cookies && req.cookies.hanko) {
        return req.cookies.hanko;
      }
      return null;
    }
  })
);

// The decoded JWT is available through the `req.auth` property (the exact property
// making it available can be customized through the `requestProperty` option on
// middleware creation).
// see: https://github.com/auth0/express-jwt#api
app.get('/protected', function (req, res) {
  if (!req.auth.sub) return res.sendStatus(401);
  res.sendStatus(200);
});
```

```mdx-code-block
</TabItem>
<TabItem value="deno" label="Deno">
```

This is an example of using an authentication middleware in a [Fresh](https://fresh.deno.dev) app using the [jose](https://github.com/panva/jose/) package:

```javascript showLineNumbers
import { MiddlewareHandlerContext } from "$fresh/server.ts";
import { getCookies } from "$std/http/cookie.ts";
import * as jose from 'https://deno.land/x/jose@v4.14.4/index.ts';

interface AuthState {
  auth: jose.JWTPayload;
}

const JWKS_ENDPOINT = `${Deno.env.get("HANKO_API_URL")}/.well-known/jwks.json`;

const JWKS = jose.createRemoteJWKSet(new URL(JWKS_ENDPOINT), {
  cooldownDuration: 120000,
});

function getToken(req: Request): string | undefined {
  const cookies = getCookies(req.headers);
  const authorization = req.headers.get("authorization");

  if (authorization && authorization.split(" ")[0] === "Bearer")
    return authorization.split(" ")[1]
  else if (cookies.hanko)
    return cookies.hanko
}

export async function handler(req: Request, ctx: MiddlewareHandlerContext<AuthState>) {
  const jwt = getToken(req);

  if (jwt) {
    try {
      const { payload } = await jose.jwtVerify(jwt, JWKS);
      ctx.state.auth = payload;

      return await ctx.next();
    } catch (error) {
      return new Response("Invalid token", { status: 403 });
    }
  }

  return new Response("Missing token", { status: 403 });
};
```

```mdx-code-block
</TabItem>

<TabItem value="python" label="Python">
```
This is an example of using a custom middleware in a [FastAPI](https://fastapi.tiangolo.com/) based backend using the [PyJWT](https://pyjwt.readthedocs.io/en/stable/) package:

```python showLineNumbers
from typing import Any
import os
import jwt
from fastapi import FastAPI, Request
from fastapi.responses import JSONResponse

HANKO_API_URL = os.environ.get("HANKO_API_URL")


def deny():
    return JSONResponse(content={"error": "Unauthorized"}, status_code=401)


def extract_token_from_header(header: str) -> str:
    parts = header.split()
    return parts[1] if len(parts) == 2 and parts[0].lower() == "bearer" else None


app = FastAPI()


@app.middleware("http")
async def auth(request: Request, call_next: Any):
    authorization = request.headers.get("authorization")

    if not authorization:
        return deny()

    token = extract_token_from_header(authorization)

    if not token:
        return deny()

    try:
         # Disable SSL certificate verification while in development

        ssl_context = ssl.create_default_context()
        ssl_context.check_hostname = False
        ssl_context.verify_mode = ssl.CERT_NONE

        jwks_client = jwt.PyJWKClient(
            HANKO_API_URL + "/.well-known/jwks.json", ssl_context=ssl_context
        )
        jwks_client = jwt.PyJWKClient(
            HANKO_API_URL + "/.well-known/jwks.json", ssl_context=ssl_context
        )
        signing_key = jwks_client.get_signing_key_from_jwt(token)
        data = jwt.decode(
            token,
            signing_key.key,
            algorithms=["RS256"],
            audience="localhost",
        )

        if not data:
            return deny()

        return await call_next(request)

    except (jwt.DecodeError, Exception) as e:
        print(f"Authentication error: {e}")
        return deny()


@app.get("/")
async def root():
    return {"message": "Hello World"}


@app.get("/protected")
async def protected():
    return {"message": "Hello World"}


```

```mdx-code-block
</TabItem>

</Tabs>
```
